/**
 * Create in 2012-9-4 上午11:55:39.
 */
package kit4j.util.swing;

import java.applet.Applet;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Frame;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.lang.reflect.Method;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;

/**
 * Swing组件操作工具类。
 * 
 * @author MiniKnife
 * 
 */
public abstract class SwingUtil {

	/**
	 * 默认窗体宽度。
	 */
	private static final int DEFAULT_FRAME_WIDTH = 800;

	/**
	 * 默认窗体高度。
	 */
	private static final int DEFAULT_FRAME_HEIGHT = 600;

	/**
	 * 最后一个进行显示的窗体。
	 */
	private static JFrame lastShownFrame = null;

	/**
	 * 最后一个进行显示的窗体对象锁。
	 */
	private static Object lastShownFrameLock = new Object();

	/**
	 * @return 最后一个进行显示的窗体对象。
	 */
	public static JFrame getLastFrame() {
		synchronized (lastShownFrameLock) {
			return lastShownFrame;
		}
	}

	/**
	 * 在窗体中显示给定的组件。
	 * 
	 * @param comps
	 *            要显示的组件
	 */
	public static void showComponent(final Component... comps) {
		showComponent(new FlowLayout(), comps);
	}

	/**
	 * 使用给定的布局管理器在窗体中显示给定的组件。
	 * 
	 * @param layout
	 *            要给窗体设置的布局管理器对象
	 * @param comps
	 *            要显示的组件
	 */
	public static void showComponent(final LayoutManager layout, final Component... comps) {
		JFrame frame = createFrame();
		frame.setLayout(layout);

		for (Component comp : comps) {
			frame.add(comp);
		}

		showFrame(frame);
	}

	/**
	 * 创建一个具有默认属性的窗体对象。
	 * 
	 * @return 窗体对象。
	 */
	public static JFrame createFrame() {
		JFrame frame = new JFrame();

		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.setSize(DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT);

		return frame;
	}

	/**
	 * 显示给定的窗体。
	 * 
	 * @param frame
	 *            要显示的窗体
	 */
	public static void showFrame(final JFrame frame) {
		synchronized (lastShownFrameLock) {
			lastShownFrame = frame;

			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					frame.setLocationRelativeTo(null);
					frame.setVisible(true);
				}
			});
		}
	}

	/**
	 * 在窗体中显示给定的容器组件。
	 * 
	 * @param container
	 *            要显示的容器组件
	 */
	public static void showComponent(final Container container) {
		JFrame frame = createFrame();

		frame.add(container);

		showFrame(frame);
	}

	/**
	 * 在窗体中显示给定的组件。
	 * 
	 * @param comp
	 *            要显示的组件
	 */
	public static void showComponent(final JComponent comp) {
		JFrame frame = createFrame();

		frame.add(comp);

		showFrame(frame);
	}

	/**
	 * 验证并重绘指定的容器。如果指定的容器存在父容器，则直接验证并重绘父容器。
	 * 
	 * @param container
	 *            要执行操作的容器
	 */
	public static void validateAndRepaintContainer(Container container) {
		Container parent = container.getParent();

		if (parent != null) {
			parent.validate();
			parent.repaint();
		} else {
			container.validate();
			container.repaint();
		}
	}

	/**
	 * 获取给定组件原点相对窗体的根容器的x坐标。
	 * 
	 * @param comp
	 *            要获取x坐标的组件
	 * @return 组件原点相对窗体的根容器的x坐标。
	 */
	public static int getXFromRootContainer(Component comp) {
		int x = comp.getX();
		Container container = comp.getParent();
		Container parent = container.getParent();

		while (parent != null && !isWindowForm(parent)) {
			x += container.getX();
			container = parent;
			parent = container.getParent();
		}

		return x;
	}

	/**
	 * 判断给定的组件是否是窗体对象。
	 * 
	 * @param comp
	 *            要判断是否为窗体的组件
	 * @return 如果给定的组件是窗体对象，则返回true，否则返回false。
	 */
	private static boolean isWindowForm(Component comp) {
		return comp instanceof Window || comp instanceof Applet;
	}

	/**
	 * 获取给定组件原点相对窗体的根容器的y坐标。
	 * 
	 * @param comp
	 *            要获取y坐标的组件
	 * @return 组件原点相对窗体的根容器的y坐标。
	 */
	public static int getYFromRootContainer(Component comp) {
		int y = comp.getY();
		Container container = comp.getParent();
		Container parent = container.getParent();

		while (parent != null && !isWindowForm(parent)) {
			y += container.getY();
			container = parent;
			parent = container.getParent();
		}

		return y;
	}

	/**
	 * 获取给定组件原点相对窗体的根容器的位置。
	 * 
	 * @param comp
	 *            要获取位置的组件
	 * @return 组件原点相对窗体的根容器的位置。
	 */
	public static Point getLocationFromRootContainer(Component comp) {
		int x = comp.getX();
		int y = comp.getY();
		Container container = comp.getParent();
		Container parent = container.getParent();

		while (parent != null && !isWindowForm(parent)) {
			x += container.getX();
			y += container.getY();
			container = parent;
			parent = container.getParent();
		}

		return new Point(x, y);
	}

	/**
	 * 获取给定组件原点相对窗体的x坐标。<br />
	 * 该方法的计算结果与{@link #getXFromRootContainer(Component)}的区别是该方法的计算会包括窗体的边框尺寸，而{@link #getXFromRootContainer(Component)}
	 * 方法不会。
	 * 
	 * @param comp
	 *            要获取x坐标的组件
	 * @return 组件原点相对窗体的x坐标。
	 */
	public static int getXFromWindowForm(Component comp) {
		int x = comp.getX();
		Container container = comp.getParent();
		Container parent = container.getParent();

		while (parent != null) {
			x += container.getX();
			container = parent;
			parent = container.getParent();
		}

		return x;
	}

	/**
	 * 获取给定组件原点相对窗体的y坐标。<br />
	 * 该方法的计算结果与{@link #getYFromRootContainer(Component)}的区别是该方法的计算会包括窗体的边框尺寸，而{@link #getYFromRootContainer(Component)}
	 * 方法不会。
	 * 
	 * @param comp
	 *            要获取y坐标的组件
	 * @return 组件原点相对窗体的y坐标。
	 */
	public static int getYFromWindowForm(Component comp) {
		int y = comp.getY();
		Container container = comp.getParent();
		Container parent = container.getParent();

		while (parent != null) {
			y += container.getY();
			container = parent;
			parent = container.getParent();
		}

		return y;
	}

	/**
	 * 获取给定组件原点相对窗体的位置。<br />
	 * 该方法的计算结果与{@link #getLocationFromRootContainer(Component)}的区别是该方法的计算会包括窗体的边框尺寸，而
	 * {@link #getLocationFromRootContainer(Component)} 方法不会。
	 * 
	 * @param comp
	 *            要获取位置的组件
	 * @return 组件原点相对窗体的位置。
	 */
	public static Point getLocationFromWindowForm(Component comp) {
		int x = comp.getX();
		int y = comp.getY();
		Container container = comp.getParent();
		Container parent = container.getParent();

		while (parent != null) {
			x += container.getX();
			y += container.getY();
			container = parent;
			parent = container.getParent();
		}

		return new Point(x, y);
	}

	/**
	 * 设置给定窗口禁用或启动窗口装饰。
	 * 
	 * @param frame
	 *            窗口对象
	 * @param undecorated
	 *            是否禁用窗口装饰
	 */
	public static void setUndecorated(Frame frame, boolean undecorated) {
		dispose(frame);
		frame.setUndecorated(undecorated);
		frame.setVisible(true);
	}

	/**
	 * 设置给定对话框禁用或启动窗口装饰。
	 * 
	 * @param dialog
	 *            对话框对象
	 * @param undecorated
	 *            是否禁用窗口装饰
	 */
	public static void setUndecorated(Dialog dialog, boolean undecorated) {
		dispose(dialog);
		dialog.setUndecorated(undecorated);
		dialog.setVisible(true);
	}

	/**
	 * 释放给定窗口的资源。
	 * 
	 * @param window
	 *            要释放资源的窗口对象
	 */
	public static void dispose(Window window) {
		if (window.isDisplayable()) {
			window.setVisible(false);
			window.dispose();
		}
	}

	/**
	 * 设置给定窗体的不透明度。
	 * 
	 * @param window
	 *            要设置不透明度的窗体
	 * @param opacity
	 *            不透明度，不透明度值范围从0~1，值越大，则不透明度越高，透明度越小
	 */
	public static void setWindowOpacity(Window window, float opacity) {
		try {
			Class<?> clazz = Class.forName("com.sun.awt.AWTUtilities");
			Method m = clazz.getMethod("setWindowOpacity", Window.class, Float.TYPE);
			
			m.invoke(null, window, opacity);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 设置给定窗体是否不透明。
	 * 
	 * @param window
	 *            要设置是否不透明的窗体
	 * @param opaque
	 *            true为不透明，false为透明
	 */
	public static void setWindowOpaque(Window window, boolean opaque) {
		try {
			Class<?> clazz = Class.forName("com.sun.awt.AWTUtilities");
			Method m = clazz.getMethod("setWindowOpaque", Window.class, Boolean.TYPE);
			
			m.invoke(null, window, opaque);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 重设swing组件的默认字体。
	 * 
	 * @param defaultFont
	 *            要设置的字体
	 */
	public static void resetDefaultFont(Font defaultFont) {
		resetDefaultFont(new FontUIResource(defaultFont));
	}

	/**
	 * 重设swing组件的默认字体。
	 * 
	 * @param defaultFont
	 *            要设置的字体
	 */
	public static void resetDefaultFont(FontUIResource defaultFont) {
		UIManager.put("ToolTip.font", defaultFont);
		UIManager.put("Table.font", defaultFont);
		UIManager.put("TableHeader.font", defaultFont);
		UIManager.put("TextField.font", defaultFont);
		UIManager.put("ComboBox.font", defaultFont);
		UIManager.put("TextField.font", defaultFont);
		UIManager.put("PasswordField.font", defaultFont);
		UIManager.put("TextArea.font", defaultFont);
		UIManager.put("TextPane.font", defaultFont);
		UIManager.put("EditorPane.font", defaultFont);
		UIManager.put("FormattedTextField.font", defaultFont);
		UIManager.put("Button.font", defaultFont);
		UIManager.put("CheckBox.font", defaultFont);
		UIManager.put("RadioButton.font", defaultFont);
		UIManager.put("ToggleButton.font", defaultFont);
		UIManager.put("ProgressBar.font", defaultFont);
		UIManager.put("DesktopIcon.font", defaultFont);
		UIManager.put("TitledBorder.font", defaultFont);
		UIManager.put("Label.font", defaultFont);
		UIManager.put("List.font", defaultFont);
		UIManager.put("TabbedPane.font", defaultFont);
		UIManager.put("MenuBar.font", defaultFont);
		UIManager.put("Menu.font", defaultFont);
		UIManager.put("MenuItem.font", defaultFont);
		UIManager.put("PopupMenu.font", defaultFont);
		UIManager.put("CheckBoxMenuItem.font", defaultFont);
		UIManager.put("RadioButtonMenuItem.font", defaultFont);
		UIManager.put("Spinner.font", defaultFont);
		UIManager.put("Tree.font", defaultFont);
		UIManager.put("ToolBar.font", defaultFont);
		UIManager.put("OptionPane.messageFont", defaultFont);
		UIManager.put("OptionPane.buttonFont", defaultFont);
	}

	public static boolean redispatchMouseEvent(Container container, MouseEvent evt) {
		Point point = evt.getPoint();

		Component component = container.getComponentAt(point);

		if (component != null) {
			Point componentPoint = SwingUtilities.convertPoint(container, point, component);
			boolean containsPoint = component.contains(componentPoint);

			if (containsPoint) {
				if (component != container && component instanceof Container
						&& redispatchMouseEvent((Container) component, evt)) {
					return true;
				} else {
					int eventID = evt.getID();
					component.dispatchEvent(new MouseEvent(component, eventID, evt.getWhen(), evt.getModifiers(),
							componentPoint.x, componentPoint.y, evt.getClickCount(), evt.isPopupTrigger()));

					return true;
				}
			}
		}

		return false;
	}
}
